package com.woniu.atms.controller.pre;

import com.woniu.atms.entity.*;
import com.woniu.atms.mapper.RouteMapper;
import com.woniu.atms.mapper.SiteMapper;
import com.woniu.atms.pojo.pre.buyticket.*;
import com.woniu.atms.service.*;
import com.woniu.atms.util.*;
import com.woniu.atms.vo.pre.buyticket.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author zjh
 * @title: PreBuyTicketController 用户买票管理（用户操作区，面向外界，需考虑各种因素）
 * @projectName g5_atms
 * @description: TODO
 * @date 2022/1/17 15:11
 */
@Api(tags = "用户买票管理（前台）")
@RestController
@RequestMapping("/api/pre/v1/buy_ticket")
public class PreBuyTicketController {
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private SiteMapper siteMapper;
    @Autowired
    private ScheduleService scheduleService;
    @Autowired
    private RouteMapper routeMapper;
    @Autowired
    private CarService carService;
    @Autowired
    private CarTypeService carTypeService;
    @Autowired
    private CarSeatService carSeatService;
    @Autowired
    private G5PassengerService passengerService;
    @Autowired
    private G5OrderService g5OrderService;
    @Autowired
    private G5TicketSiteService ticketSiteService;
    @Autowired
    private G5TicketService ticketService;
    @Autowired
    private G5UserService userService;
    @Autowired
    private Audience audience;


    /**
     * 用户在输入时进行的站点名提示（模糊查询匹配）
     * 需求权限：公开
     * 预估访问频次：极高
     * 缓存策略：结果缓存+子查询缓存
     * @param vo 用户输入的数据视图
     * @return 返回站点信息的列表
     */
    @ApiOperation(value = "用户模糊查找站点名",notes = "用户输入框提示匹配用，输入视图为关键字，返回为匹配的站点POJO")
    @PostMapping("/site_fuzzy_search")
    public ResponseResult<List<SitePOJO>> siteFuzzySearch(@RequestBody @Valid SiteFuzzySearchVO vo) {
        //得到请求的关键字（去首尾空）
        String keyWord = vo.getKeyWord().trim();
        //新建结果字串
        List<SitePOJO> resList = null;
        try{
            //查询缓存
            Object o = redisTemplate.opsForValue().get("buyTicket_controller_site_cache_res_" + keyWord);
            if(o != null){
                resList = (List<SitePOJO>)o;
            }
        }catch (Throwable t){
            //TODO 待商榷如何处理异常
            System.out.println("查询失败：Redis服务器出错[1]");
        }
        //如果不存在缓存，则继续
        if (resList == null) {
            //初始化结果为空数组
            resList = new ArrayList<>();
            //设置初始信号
            boolean firstSign = true;
            //对每个字符进行查询，合并查询结果
            for (char sub : keyWord.toCharArray()) {
                //生成子字符串
                String subStr = String.valueOf(sub);
                //如果为空则跳过并继续
                if (" ".equals(subStr) || "".equals(subStr))
                    continue;
                //新建临时数组并到缓存中查询
                List<SitePOJO> tp = null;
                try{
                    Object o = redisTemplate.opsForValue().get("buyTicket_controller_site_cache_map_" + subStr);
                    if(o != null){
                        tp = (List<SitePOJO>)o;
                    }
                }catch (Throwable t){
                    //TODO 待商榷如何处理异常
                    System.out.println("查询失败：Redis服务器出错[2]");
                }
                //如果缓存没有数据则查询数据库
                if (tp == null) {
                    //为tp创建空白数组
                    tp = new ArrayList<>();
                    //查询数据库
                    List<G5Site> tpSearch = siteMapper.findSiteByFuzzyNameOrAddress(subStr);
                    //如果查询结果不为null
                    if(tpSearch != null){
                        //对元素进行pojo转换,并进行添加
                        for(G5Site site:tpSearch){
                            SitePOJO pojo = new SitePOJO();
                            BeanUtils.copyProperties(site, pojo);
                            tp.add(pojo);
                        }
                    }
                    //增加到缓存
                    try{
                        redisTemplate.opsForValue().set("buyTicket_controller_site_cache_map_" + subStr, tp);
                    }catch (Throwable t){
                        //TODO 待商榷如何处理异常
                        System.out.println("查询失败：Redis服务器出错[3]");
                    }

                }
                //如果是初始状态则直接并集,否则为求交集
                if (firstSign) {
                    resList.addAll(tp);
                    firstSign = false;
                } else {
                    resList.retainAll(tp);
                }
                //如果结果已为空数组，则跳出循环
                if (resList.size() == 0)
                    break;
            }
            //对结果进行缓存
            try{
                redisTemplate.opsForValue().set("buyTicket_controller_site_cache_res_" + keyWord, resList);
            }catch (Throwable t){
                //TODO 待商榷如何处理异常
                System.out.println("查询失败：Redis服务器出错[4]");
            }
            return new ResponseResult<>(resList);
        } else {
            return new ResponseResult<>(resList);
        }
    }

    /**
     * 输入起始点和目标点，模糊查询所有符合的班次
     * 需求权限：公开
     * 预计访问频次：高
     * 缓存策略：基于LRU的缓存策略，对查询结果和中间步骤进行缓存
     * @param vo 用户输入的数据视图
     * @return 返回班次信息数据视图列表
     */
    @ApiOperation(value = "用户输入起止站点查询所有符合线路",notes = "展示搜索结果用，输入视图起始和终点关键字，返回为匹配的线路POJO")
    @PostMapping("/schedule_list")
    public ResponseResult<List<ScheduleSimplePOJO>> listWithStartEnd(@RequestBody @Valid ListWithStartEndVO vo){
        List<ScheduleSimplePOJO> resList = null;
        try{
            //查询结果缓存
            Object o = redisTemplate.opsForValue().get("buyTicket_controller_search_se_cache_res_" + vo.getStartKeyWord()+"_"+vo.getEndKeyWord());
            if(o != null){
                resList = (List<ScheduleSimplePOJO>)o;
            }
        }catch (Throwable t){
            //TODO 待商榷如何处理异常
            System.out.println("查询失败：Redis服务器出错[5]");
        }
        if(resList == null){
            resList = new ArrayList<>();
            //通过vo获取用户数据，对起始和终止分别使用模糊查询
            List<SitePOJO> startSidePOJO = siteFuzzySearch(new SiteFuzzySearchVO(){{setKeyWord(vo.getStartKeyWord());}}).getData();
            List<SitePOJO> endSidePOJO = siteFuzzySearch(new SiteFuzzySearchVO(){{setKeyWord(vo.getEndKeyWord());}}).getData();
            //如果返回null或长度为0则直接返回空结果
            if(startSidePOJO == null || startSidePOJO.size() == 0 || endSidePOJO == null || endSidePOJO.size() == 0){
                //设置结果缓存
                try{
                    redisTemplate.opsForValue().set("buyTicket_controller_search_se_cache_res_" + vo.getStartKeyWord()+"_"+vo.getEndKeyWord(), resList);
                }catch (Throwable t){
                    //TODO 待商榷如何处理异常
                    System.out.println("查询失败：Redis服务器出错[10]");
                }
                return new ResponseResult<>(resList);
            }
            //将Pojo转换为常规对象数组
            List<Integer> startSiteList = new ArrayList<>();
            for(SitePOJO pojo:startSidePOJO){
                startSiteList.add(pojo.getId());
            }
            List<Integer> endSiteList = new ArrayList<>();
            for(SitePOJO pojo:endSidePOJO){
                endSiteList.add(pojo.getId());
            }
            //调用查询所有线路
            //注：此处接口返回的数据为动态代理数据（懒加载），无法进行缓存操作【缓存后再次获取将失去代理（注解上声明代理Handler不被序列化）】
            List<G5Route> routeList = routeMapper.findRoutesByStarAndEndAddress(startSiteList, endSiteList);
            routeList = routeList.stream().filter(t->"y".equals(t.getIsExit()) && "y".equals(t.getStatus())).collect(Collectors.toList());
            //如果没有线路，则返回空数组
            if(routeList == null || routeList.size() ==0){
                //设置结果缓存
                try{
                    redisTemplate.opsForValue().set("buyTicket_controller_search_se_cache_res_" + vo.getStartKeyWord()+"_"+vo.getEndKeyWord(), resList);
                }catch (Throwable t){
                    //TODO 待商榷如何处理异常
                    System.out.println("查询失败：Redis服务器出错[10]");
                }
                return new ResponseResult<>(resList);
            }
            //查询线路表对应所有班次表
            List<G5Schedule> scheduleList = null;
            try{
                //查询缓存
                Object o = redisTemplate.opsForValue().get("buyTicket_controller_search_cache_scheduleList_" + DataUtils.getMD5(routeList.toString()));
                if(o != null){
                    scheduleList = (List<G5Schedule>)o;
                }
            }catch (Throwable t){
                //TODO 待商榷如何处理异常
                System.out.println("查询失败：Redis服务器出错[8]");
            }
            if(scheduleList == null){
                List<Integer> rids= new ArrayList<>();
                routeList.forEach(t->rids.add(t.getId()));
                //数据库查询
                scheduleList = scheduleService.findByRouteList(rids);
                //过滤掉无效数据
                scheduleList = scheduleList.stream().filter(t->"y".equals(t.getIsExit()) && "y".equals(t.getStatus())).collect(Collectors.toList());
                try{
                    redisTemplate.opsForValue().set("buyTicket_controller_search_cache_scheduleList_" + DataUtils.getMD5(scheduleList.toString()), scheduleList);
                }catch (Throwable t){
                    //TODO 待商榷如何处理异常
                    System.out.println("查询失败：Redis服务器出错[9]");
                }
            }
            //如果没有线路，则返回空数组
            if(scheduleList == null || scheduleList.size() ==0){
                //设置结果缓存
                try{
                    redisTemplate.opsForValue().set("buyTicket_controller_search_se_cache_res_" + vo.getStartKeyWord()+"_"+vo.getEndKeyWord(), resList);
                }catch (Throwable t){
                    //TODO 待商榷如何处理异常
                    System.out.println("查询失败：Redis服务器出错[10]");
                }
                return new ResponseResult<>(resList);
            }
            //将班次列表转换为POJO对象
            for(G5Schedule schedule:scheduleList){
                resList.add(getSimpleInfoWithScheduleId(schedule));
            }

        }
        //设置结果缓存
        try{
            redisTemplate.opsForValue().set("buyTicket_controller_search_se_cache_res_" + vo.getStartKeyWord()+"_"+vo.getEndKeyWord(), resList);
        }catch (Throwable t){
            //TODO 待商榷如何处理异常
            System.out.println("查询失败：Redis服务器出错[10]");
        }
        return new ResponseResult<>(resList);
    }


    /**
     * 获取班次的简单关联信息，返回整合信息pojo
     * 权限需求：公开
     * 预计访问频次：高
     * 缓存策略：根据id进行全部缓存（供后续全部访问使用）
     * @param schedule 班次单实体类
     * @return 返回综合信息表示视图
     */
    private ScheduleSimplePOJO getSimpleInfoWithScheduleId(G5Schedule schedule){
        //新建返回POJO对象
        ScheduleSimplePOJO pojo = new ScheduleSimplePOJO();
        //将班次信息赋值
        pojo.setId(schedule.getId());
        pojo.setRunTime(schedule.getTime());
        pojo.setPrice(schedule.getPrice());
        //查询线路表信息
        G5Route route = null;
        try{
            //TODO 此处采用动态代理，有小问题（首次抛出异常），暂时未发现明显问题，待后续测试
            Object o = redisTemplate.opsForValue().get("buyTicket_controller_db_cache_route_" + schedule.getRouteId());
            if(o != null){
                route = (G5Route) o;
            }
        }catch (Throwable t){
            //TODO 待商榷如何处理异常
            System.out.println("查询失败：Redis服务器出错[11]");
        }
        if(route == null){
            //数据库查询
            route = routeMapper.findRouteById(schedule.getRouteId());
            try{
                //设置缓存数据（暂不考虑缓存穿透等情况）
                redisTemplate.opsForValue().set("buyTicket_controller_db_cache_route_" + schedule.getRouteId(), route);
            }catch (Throwable t){
                //TODO 待商榷如何处理异常
                System.out.println("设置失败：Redis服务器出错[12]");
            }
        }
        if(route != null && "y".equals(route.getStatus()) && "y".equals(route.getIsExit())){
            //设置线路相关属性
            pojo.setRouteName(route.getRouteName());
            pojo.setOverTime(route.getSpendTime());

            //设置站点信息
            pojo.setStartSiteName(route.getStartSite().getName());
            pojo.setEndSiteName(route.getEndSite().getName());
        }

        //查询车辆信息
        G5Car car = null;
        try{
            Object o = redisTemplate.opsForValue().get("buyTicket_controller_db_cache_car_" + schedule.getCarId());
            if(o != null){
                car = (G5Car) o;
            }
        }catch (Throwable t){
            //TODO 待商榷如何处理异常
            System.out.println("查询失败：Redis服务器出错[13]");
        }
        if(car == null){
            //数据库查询
            car = carService.queryById(schedule.getRouteId());
            try{
                //设置缓存数据（暂不考虑缓存穿透等情况）
                redisTemplate.opsForValue().set("buyTicket_controller_db_cache_car_" + schedule.getCarId(), car);
            }catch (Throwable t){
                //TODO 待商榷如何处理异常
                System.out.println("设置失败：Redis服务器出错[14]");
            }
        }
        if(car != null && "y".equals(car.getStatus()) && "y".equals(car.getIsExit())){
            //设置车辆信息
            pojo.setCarNum(car.getCarNum());

            //查询车辆类型
            G5CarType carType = null;
            try{
                Object o = redisTemplate.opsForValue().get("buyTicket_controller_db_cache_carType_" + car.getTypeId());
                if(o != null){
                    carType = (G5CarType) o;
                }
            }catch (Throwable t){
                //TODO 待商榷如何处理异常
                System.out.println("查询失败：Redis服务器出错[15]");
            }
            if(carType == null){
                //数据库查询
                carType = carTypeService.findCarTypeById(car.getTypeId());
                try{
                    //设置缓存数据（暂不考虑缓存穿透等情况）
                    redisTemplate.opsForValue().set("buyTicket_controller_db_cache_carTYpe_" + car.getTypeId(), carType);
                }catch (Throwable t){
                    //TODO 待商榷如何处理异常
                    System.out.println("设置失败：Redis服务器出错[16]");
                }
            }
            if(carType != null && "y".equals(carType.getIsExit())){
                //设置查询到的车辆类型数据
                pojo.setCarTypeName(carType.getName());
            }
        }

        return pojo;
    }

    /**
     * 查询班次的详细，返回班次的POJO
     * 权限需求：公开
     * 预计访问频次：中高
     * 缓存策略：根据id缓存pojo数据,如发生缓存穿透则设置定值阻止反复尝试
     * @param vo 输入的数据视图，内容为ID
     * @return 返回班次POJO信息
     */
    @ApiOperation(value = "用户查询班次详细接口",notes = "返回班次详细信息，考虑到详细页是单独页面，对其中外键返回id值，客户端自行查询保证数据实时性，请求数据视图为班次的id（查询返回时附带），返回为班次的POJO信息")
    @RequestMapping("/schedule_info")
    public ResponseResult<SchedulePOJO> scheduleInfo(@RequestBody @Valid ScheduleInfoVO vo){
        SchedulePOJO pojo = null;
        try{
            Object o = redisTemplate.opsForValue().get("buyTicket_controller_db_cache_schedule_" + vo.getId());
            if(o instanceof String){
                //缓存穿透，直接返回失败
                return ResponseResult.FAIL;
            }
            if(o != null){
                pojo = (SchedulePOJO) o;
            }
        }catch (Throwable t){
            //TODO 待商榷如何处理异常
            System.out.println("查询失败：Redis服务器出错[17]");
        }
        if(pojo == null){
            //数据库查询
            G5Schedule schedule = scheduleService.findById(vo.getId());
            if(schedule == null || !"y".equals(schedule.getStatus()) || !"y".equals(schedule.getIsExit())){
                //发生了查询不存在值,或查询失效的数据
                try{
                    //设置缓存穿透Flag,有效期120秒
                    redisTemplate.opsForValue().set("buyTicket_controller_db_cache_schedule_" + vo.getId(), "ERROR",120, TimeUnit.SECONDS);
                }catch (Throwable t){
                    //TODO 待商榷如何处理异常
                    System.out.println("设置失败：Redis服务器出错[18]");
                }finally {
                    return ResponseResult.FAIL;
                }
            }
            //将实体类转换为pojo
            pojo = new SchedulePOJO();
            BeanUtils.copyProperties(schedule, pojo);
            try{
                //设置缓存数据
                redisTemplate.opsForValue().set("buyTicket_controller_db_cache_schedule_" + vo.getId(), pojo);
            }catch (Throwable t){
                //TODO 待商榷如何处理异常
                System.out.println("设置失败：Redis服务器出错[19]");
            }
        }
        return new ResponseResult<>(pojo);
    }

    /**
     * 查询班次的座位表，返回座位POJO的LIST
     * 权限需求：公开
     * 预计访问频次：中
     * 缓存策略：因变动可能频繁，设置短期存储（60秒）
     * @param vo 输入的数据视图，内容为ID
     * @return 返回座位POJO信息表
     */
    @ApiOperation(value = "用户查询班次座位接口",notes = "返回座位详细信息，客户端自行查询，请求数据视图为班次的id（同上），返回为座位的ListPOJO信息")
    @RequestMapping("/seat_list")
    public ResponseResult<List<Integer>> scheduleSetList(@RequestBody @Valid ScheduleSetListVO vo){
        //根据班次id查询班次(有效性判断与发车时间判断)
        SchedulePOJO pojo = scheduleInfo(new ScheduleInfoVO(){{setId(vo.getId());}}).getData();
        //如果班次无效则直接返回
        if(pojo == null){
            return ResponseResult.FAIL;
        }
        //判断选择时间是否大于当前,如果是则返回失败
        LocalDate currDate = LocalDate.now();
        if(currDate.isAfter(vo.getDate())){
            return ResponseResult.FAIL;
        }else if(currDate.isEqual(vo.getDate())){
            //如果是当前日期,则判断班次是否已发车,如果已发车则返回失败
            LocalTime currTime = LocalTime.now();
            if(!currTime.isBefore(pojo.getTime())){
                return ResponseResult.FAIL;
            }
        }
        //查询缓存
        List<Integer> resList = null;
        try{
            Object o = redisTemplate.opsForValue().get("buyTicket_controller_res_cache_setList_" + vo.getDate().toString() + "_" + vo.getId());
            if(o != null){
                resList = (List<Integer>) o;
            }
        }catch (Throwable t){
            //TODO 待商榷如何处理异常
            System.out.println("读取失败：Redis服务器出错[20]");
        }
        if(resList == null){
            //查询数据库
            resList = carSeatService.queryUseSeatIdByDateAndSId(pojo.getId(), vo.getDate());
            //设置缓存
            try{
                //设置缓存数据（60秒时效）
                redisTemplate.opsForValue().set("buyTicket_controller_res_cache_setList_" + vo.getDate().toString() + "_" + vo.getId(), resList,60,TimeUnit.SECONDS);
            }catch (Throwable t){
                //TODO 待商榷如何处理异常
                System.out.println("设置失败：Redis服务器出错[21]");
            }
        }
        return new ResponseResult<>(resList);
    }

    /**
     * 查询班次的座位表，返回座位POJO的LIST
     * 权限需求：需要登陆
     * 预计访问频次：中
     * 缓存策略：不缓存
     * 事务处理：是
     * @param vo 输入的数据视图
     * @return 返回生成的订单号和id
     */
    @ApiOperation(value = "用户买票接口",notes = "提交成功返回订单号码，请求数据视图为班次、日期、座位和乘车人，返回为订单号和订单id")
    @RequestMapping("/buy_ticket")
    @Transactional
    public ResponseResult buyTicket(@RequestBody @Valid BuyTicketVO vo){
        //TODO 用户校验
        //获取用户信息（session/jwt等）
        Integer userId = JwtUtil.parseJwtForId(vo.getToken(), audience);
        //使用缓存记录用户id防止重复提交和恶意攻击刷票（20秒）
        try {
            Object o = redisTemplate.opsForValue().get("buyTicket_Buy_userid_" + userId);
            if (o != null) {
                return new ResponseResult<>(Code.FAIL, "你操作的太频繁了，请20秒后再试");
            }
        }catch(Throwable t){
            return new ResponseResult<>(Code.FAIL, "系统繁忙，请稍后再试");
        }
        try{
            //设置缓存数据（60秒时效）
            redisTemplate.opsForValue().set("buyTicket_Buy_userid_" + userId, "flag",20,TimeUnit.SECONDS);
        }catch (Throwable t){
            return new ResponseResult<>(Code.FAIL, "系统繁忙，请稍后再试");
        }
        //用户有效性（状态/删除）校验
        G5User user = userService.getById(userId);
        if(user == null || !"y".equals(user.getIsExit()) || !"y".equals(user.getStatus())){
            return new ResponseResult<>(Code.FAIL, "用户不存在/状态异常");
        }
        //买票有效性（封禁）校验

        //班次信息校验
        SchedulePOJO pojo = scheduleInfo(new ScheduleInfoVO(){{setId(vo.getScheduleId());}}).getData();
        //如果班次无效则直接返回
        if(pojo == null){
            return new ResponseResult<>(Code.FAIL,"班次信息无效，请刷新页面后重试");
        }
        //班次时间校验
        //日期校验
        LocalDate currDate = LocalDate.now();
        if(currDate.isAfter(vo.getDate())){
            return new ResponseResult<>(Code.FAIL,"日期选择无效，请刷新页面后重试");
        }else if(currDate.isEqual(vo.getDate())){
            //如果是当前日期,则进行时间校验
            LocalTime currTime = LocalTime.now();
            if(!currTime.isBefore(pojo.getTime())){
                return new ResponseResult<>(Code.FAIL,"当前班次已发车，请刷新页面选择后续客车");
            }
        }
        //获取此班次车的车辆信息，判断是否可用
        G5Car car = carService.queryById(pojo.getRouteId());
        if(car == null || !"y".equals(car.getStatus()) || !"y".equals(car.getIsExit())) {
            return new ResponseResult<>(Code.FAIL,"班次车辆不可用，请刷新页面选择其他客车");
        }
        //获取车辆的类型信息，判断是否可用
        G5CarType carType = carTypeService.findCarTypeById(car.getTypeId());
        if (carType == null || !"y".equals(carType.getIsExit())) {
            return new ResponseResult<>(Code.FAIL,"车辆类型不可用，请刷新页面选择其他类型客车");
        }
        //检查乘车人数量和购买座位数量是否一致
        if(vo.getPassengerIds().size() != vo.getSetIds().size() || vo.getPassengerIds().size() == 0){
            return new ResponseResult<>(Code.FAIL,"填写的购票人信息和座位信息不匹配，请检查");
        }
        //获取用户账号下的乘车人
        List<G5Passenger> userPassengerList = passengerService.findByUserId(userId);
        //遍历填写的用户进行判断
        for(Integer pid:vo.getPassengerIds()){
            boolean find = false;
            for(G5Passenger passenger:userPassengerList){
                if(Objects.equals(passenger.getId(), pid)){
                    find = true;
                    //判断乘车人是否合法
                    if(!"y".equals(passenger.getIsOk()) || !"y".equals(passenger.getIsExit())){
                        return new ResponseResult<>(Code.FAIL,"乘车人：" + passenger.getName() + "(" + passenger.getIdCard() + ")因某些原因，被限制购买车票");
                    }
                }
            }
            if(!find){
                return new ResponseResult<>(Code.FAIL,"你的用户账户上不存在此乘车人，请先添加后再试");
            }
        }

        //获取车辆的所有座位id
        List<G5CarSeat> carSeatList = carSeatService.seatList(car.getId());
        //获取当前已选择的座位列表
        List<Integer> usedSetList = carSeatService.queryUseSeatIdByDateAndSId(vo.getScheduleId(), vo.getDate());
        //检查用户输入的座位ID是否合法
        for(Integer sid:vo.getSetIds()){
            boolean find = false;
            for(G5CarSeat carSeat:carSeatList){
                if(Objects.equals(carSeat.getId(), sid)){
                    find = true;
                    break;
                }
            }
            if(find){
                //如果座位合法，判断是否已被购买
                for(Integer setId:usedSetList){
                    if(Objects.equals(setId, sid)){
                        return new ResponseResult<>(Code.FAIL,"手慢了！你选择的座位被别人抢先购买了，请刷新后再选一个吧");
                    }
                }
            }else{
                //座位非车辆的座位
                return new ResponseResult<>(Code.FAIL,"非法参数");
            }
        }

        //全部校验通过，进行购票流程
        //新建总价price
        BigDecimal allPrice = new BigDecimal("0.00");
        //新建订单对象
        G5Order order = new G5Order();
        //取创建时间
        Date nowTime = new Date();
        //生成订单号
        order.setOrderNo(DataUtils.generateOrderNum(nowTime));
        order.setUserId(userId);
        order.setCreateTime(nowTime);
        order.setPayTime(null);
        order.setIsOk("x");
        order.setIsExit("y");
        //将订单写入数据库并获取id
        g5OrderService.insert(order);
        //循环生成车票
        for(int i=0;i<vo.getSetIds().size();i++){
            //创建时间车票中间表实体类
            G5TicketSite ticketSite = new G5TicketSite();
            //设置数据
            ticketSite.setDate(DataUtils.localDate2Date(vo.getDate()));
            ticketSite.setScheduleId(vo.getScheduleId());
            ticketSite.setSiteId(vo.getSetIds().get(i));
            ticketSite.setSiteStatus("x");
            ticketSite.setIsExit("y");
            //添加到数据库
            ticketSiteService.insert(ticketSite);

            //创建车票实体类
            G5Ticket ticket = new G5Ticket();
            //设置数据
            ticket.setNo(DataUtils.generateTicketNum());
            ticket.setPassengerId(vo.getPassengerIds().get(i));
            ticket.setIsPay("n");
            ticket.setIsCheck("n");
            ticket.setTimeSeatId(ticketSite.getId());
            ticket.setOrderId(order.getId());
            ticket.setReTicket("n");
            ticket.setIsExit("y");
            ticket.setPrice(pojo.getPrice());
            allPrice = allPrice.add(pojo.getPrice());
            //添加到数据库
            ticketService.insert(ticket);
        }
        //将订单价格更新
        order.setPrice(allPrice);
        g5OrderService.update(order);

        //刷新相关缓存信息（修改的表格）

        BuyTicketPOJO buyTicketPOJO = new BuyTicketPOJO();
        buyTicketPOJO.setOrderId(order.getId());
        buyTicketPOJO.setOrderNum(order.getOrderNo());

        return new ResponseResult<>(buyTicketPOJO);
    }

    /**
     * 查询路线信息的接口
     * 权限需求：开放
     * 预计访问频次：中高
     * 缓存策略：缓存所有
     * @param vo 传入的数据视图
     * @return 返回POJO信息
     */
    @ApiOperation(value = "用户获取线路信息接口",notes = "班次详情里用户获取数据的接口，传入需要获取的id，返回POJO信息")
    @RequestMapping("/get_route_info")
    public ResponseResult<RoutePOJO> getRouteInfo(@RequestBody @Valid GetRouteInfoVO vo){
        RoutePOJO pojo = new RoutePOJO();
        G5Route route = null;
        try{
            //TODO 此处采用动态代理，有小问题（首次抛出异常），暂时未发现明显问题，待后续测试
            Object o = redisTemplate.opsForValue().get("buyTicket_controller_db_cache_route_" + vo.getId());
            if(o != null){
                route = (G5Route) o;
            }
        }catch (Throwable t){
            //TODO 待商榷如何处理异常
            System.out.println("查询失败：Redis服务器出错[11]");
        }
        if(route == null){
            //数据库查询
            route = routeMapper.findRouteById(vo.getId());
            try{
                //设置缓存数据（暂不考虑缓存穿透等情况）
                redisTemplate.opsForValue().set("buyTicket_controller_db_cache_route_" + vo.getId(), route);
            }catch (Throwable t){
                //TODO 待商榷如何处理异常
                System.out.println("设置失败：Redis服务器出错[12]");
            }
        }
        if(route != null){
            pojo.setName(route.getRouteName());
            pojo.setSpendTime(route.getSpendTime());
            pojo.setStartSiteName(route.getStartSite().getName());
            pojo.setStartSiteAddress(route.getStartSite().getAddress());
            pojo.setEndSiteName(route.getEndSite().getName());
            pojo.setEndSiteAddress(route.getEndSite().getAddress());
        }
        return new ResponseResult<>(pojo);

    }

    /**
     * 查询车辆信息的接口
     * 权限需求：开放
     * 预计访问频次：中高
     * 缓存策略：缓存所有
     * @param vo 传入的数据视图
     * @return 返回POJO信息
     */
    @ApiOperation(value = "用户获取车辆信息接口",notes = "班次详情里用户获取数据的接口，传入需要获取的id，返回POJO信息")
    @RequestMapping("/get_car_info")
    public ResponseResult<CarPOJO> getCarInfo(@RequestBody @Valid GetCarInfoVO vo){
        CarPOJO pojo = new CarPOJO();
        G5Car car = null;
        try{
            //TODO 此处采用动态代理，有小问题（首次抛出异常），暂时未发现明显问题，待后续测试
            Object o = redisTemplate.opsForValue().get("buyTicket_controller_db_cache_car_" + vo.getId());
            if(o != null){
                car = (G5Car) o;
            }
        }catch (Throwable t){
            //TODO 待商榷如何处理异常
            System.out.println("查询失败：Redis服务器出错[13]");
        }
        if(car == null){
            //数据库查询
            car = carService.queryById(vo.getId());
            try{
                //设置缓存数据（暂不考虑缓存穿透等情况）
                redisTemplate.opsForValue().set("buyTicket_controller_db_cache_car_" + vo.getId(), car);
            }catch (Throwable t){
                //TODO 待商榷如何处理异常
                System.out.println("设置失败：Redis服务器出错[14]");
            }
        }
        if(car != null){
            BeanUtils.copyProperties(car, pojo);
        }
        return new ResponseResult<>(pojo);
    }

    /**
     * 查询车辆类型信息的接口
     * 权限需求：开放
     * 预计访问频次：中高
     * 缓存策略：缓存所有
     * @param vo 传入的数据视图
     * @return 返回POJO信息
     */
    @ApiOperation(value = "用户获取车辆信息接口",notes = "班次详情里用户获取数据的接口，传入需要获取的id，返回POJO信息")
    @RequestMapping("/get_carType_info")
    public ResponseResult<CarTypePOJO> getCarInfo(@RequestBody @Valid GetCarTypeInfoVO vo){
        CarTypePOJO pojo = new CarTypePOJO();
        G5CarType carType = null;
        try{
            //TODO 此处采用动态代理，有小问题（首次抛出异常），暂时未发现明显问题，待后续测试
            Object o = redisTemplate.opsForValue().get("buyTicket_controller_db_cache_carType_" + vo.getId());
            if(o != null){
                carType = (G5CarType) o;
            }
        }catch (Throwable t){
            //TODO 待商榷如何处理异常
            System.out.println("查询失败：Redis服务器出错[15]");
        }
        if(carType == null){
            //数据库查询
            carType = carTypeService.findCarTypeById(vo.getId());
            try{
                //设置缓存数据（暂不考虑缓存穿透等情况）
                redisTemplate.opsForValue().set("buyTicket_controller_db_cache_carType_" + vo.getId(), carType);
            }catch (Throwable t){
                //TODO 待商榷如何处理异常
                System.out.println("设置失败：Redis服务器出错[16]");
            }
        }
        if(carType != null){
            BeanUtils.copyProperties(carType, pojo);
        }
        return new ResponseResult<>(pojo);
    }

}
